﻿#region File Header
//-----------------------------------------------------------------------------
// SibLib
//
// Copyright (C) 2010 Julien Villers
// This program is distributed under the terms of the 
// GNU Lesser General Public License (LGPL).
// See Docs/lgpl.txt and Docs/gpl.txt
//-----------------------------------------------------------------------------
#endregion

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace SibLib.Render
{
    /// <summary>
    /// Helper to render a Quad, textured or colored.
    /// </summary>
    public class Quad
    {
        #region Vertex Data
        /// <summary>
        /// Vertexes.
        /// </summary>
        protected VertexPositionNormalTexture[] m_Vertices;
        /// <summary>
        /// Indexes inside vertex array.
        /// </summary>
        protected int[] m_Indexes;
        #endregion

        #region Texture
        /// <summary>
        /// Vertex declaration.
        /// </summary>
        protected VertexDeclaration     m_VertexDeclaration;
        /// <summary>
        /// Actual texture data.
        /// </summary>
        protected Texture2D             m_Texture;
        /// <summary>
        /// Effect to apply texture and coloring.
        /// </summary>
        protected BasicEffect           m_BasicEffect;
        #endregion

        #region Other members
        /// <summary>
        /// Reference to graphics device to render to.
        /// </summary>
        protected GraphicsDevice        m_GraphicsDevice;
        #endregion

        #region Setup
        /// <summary>
        /// Construct a Quad.
        /// </summary>
        /// <param name="graphicsDevice">Target GraphicsDevice</param>
        /// <param name="effectPool">EffectPool, can be null</param>
        public Quad(GraphicsDevice graphicsDevice, EffectPool effectPool)
        {
            // Vertex data
            m_Vertices = new VertexPositionNormalTexture[4];
            m_Indexes = new int[6];

            // Texture
            m_GraphicsDevice = graphicsDevice;
            m_BasicEffect = new BasicEffect(m_GraphicsDevice, effectPool);
            m_VertexDeclaration = new VertexDeclaration(m_GraphicsDevice, VertexPositionNormalTexture.VertexElements);
        }

        /// <summary>
        /// Internal setup method. Computes vertices.
        /// </summary>
        /// <param name="origin">Position</param>
        /// <param name="normal">Normal to visible side</param>
        /// <param name="up">Up vector</param>
        /// <param name="width">Width</param>
        /// <param name="height">Height</param>
        /// <param name="view">View matrix</param>
        /// <param name="projection">Projection matrix</param>
        protected void SetupCommon(Vector3 origin, Vector3 normal, Vector3 up, float width, float height, Matrix view, Matrix projection)
        {
            // Calculate the quad corners
            Vector3 _left           = Vector3.Cross(normal, up);
            Vector3 _uppercenter    = (up * height / 2) + origin;
            Vector3 upperLeft       = _uppercenter + (_left * width / 2);
            Vector3 upperRight      = _uppercenter - (_left * width / 2);
            Vector3 lowerLeft       = upperLeft - (up * height);
            Vector3 lowerRight      = upperRight - (up * height);

            FillVertices(normal, lowerLeft, upperLeft, lowerRight, upperRight);

            m_BasicEffect.World         = Matrix.Identity;
            m_BasicEffect.View          = view;
            m_BasicEffect.Projection    = projection;
       }
        
        /// <summary>
        /// Setup a textured Quad.
        /// </summary>
        /// <param name="origin">Position</param>
        /// <param name="normal">Normal to visible side</param>
        /// <param name="up">Up vector</param>
        /// <param name="width">Width</param>
        /// <param name="height">Height</param>
        /// <param name="view">View matrix</param>
        /// <param name="projection">Projection matrix</param>
        /// <param name="texture">Texture, can be null</param>
        public void Setup(Vector3 origin, Vector3 normal, Vector3 up, float width, float height, Matrix view, Matrix projection, Texture2D texture)
        {
            SetupCommon(origin, normal, up, width, height, view, projection);

            // Setup texture
            m_Texture = texture;
            m_BasicEffect.EnableDefaultLighting();
            
            m_BasicEffect.TextureEnabled = (texture != null);
            m_BasicEffect.Texture = texture;

            m_BasicEffect.LightingEnabled = true;
            m_BasicEffect.DiffuseColor = new Vector3(0.75f, 0.75f, 0.75f);
            m_BasicEffect.SpecularColor = new Vector3(0.75f, 0.75f, 0.75f);
            m_BasicEffect.SpecularPower = 4.0f;
            m_BasicEffect.AmbientLightColor = new Vector3(1.0f, 1.0f, 1.0f);
        }

        /// <summary>
        /// Setup a colored Quad.
        /// </summary>
        /// <param name="origin">Position</param>
        /// <param name="normal">Normal to visible side</param>
        /// <param name="up">Up vector</param>
        /// <param name="width">Width</param>
        /// <param name="height">Height</param>
        /// <param name="view">View matrix</param>
        /// <param name="projection">Projection matrix</param>
        /// <param name="color">Color to apply</param>
        public void Setup(Vector3 origin, Vector3 normal, Vector3 up, float width, float height, Matrix view, Matrix projection, Color color)
        {
            SetupCommon(origin, normal, up, width, height, view, projection);

            // No texture
            m_BasicEffect.TextureEnabled = false;

            // Colored lighting
            m_BasicEffect.LightingEnabled = true;
            m_BasicEffect.DiffuseColor = color.ToVector3();         // TODO: Have a thought about Diffuse and Specular.
            m_BasicEffect.SpecularColor = color.ToVector3();
            m_BasicEffect.SpecularPower = 5.0f;
            m_BasicEffect.AmbientLightColor = new Vector3(1.0f, 1.0f, 1.0f);
        }
        
        private void FillVertices(Vector3 normal, Vector3 lowerLeft, Vector3 upperLeft, Vector3 lowerRight, Vector3 upperRight)
        {
            // Fill in texture coordinates to display full texture on quad
            Vector2 textureUpperLeft    = new Vector2( 0.0f, 0.0f );
            Vector2 textureUpperRight   = new Vector2( 1.0f, 0.0f );
            Vector2 textureLowerLeft    = new Vector2( 0.0f, 1.0f );
            Vector2 textureLowerRight   = new Vector2( 1.0f, 1.0f );

            // Provide a normal for each vertex
            for (int i = 0; i != m_Vertices.Length; i++)
            {
                m_Vertices[i].Normal = normal;
            }

            // Set the position and texture coordinate for each vertex
            m_Vertices[0].Position = lowerLeft;
            m_Vertices[0].TextureCoordinate = textureLowerLeft;
            m_Vertices[1].Position = upperLeft;
            m_Vertices[1].TextureCoordinate = textureUpperLeft;
            m_Vertices[2].Position = lowerRight;
            m_Vertices[2].TextureCoordinate = textureLowerRight;
            m_Vertices[3].Position = upperRight;
            m_Vertices[3].TextureCoordinate = textureUpperRight;

            // Set the index buffer for each vertex, using clockwise winding
            m_Indexes[0] = 0;
            m_Indexes[1] = 1;
            m_Indexes[2] = 2;
            m_Indexes[3] = 2;
            m_Indexes[4] = 1;
            m_Indexes[5] = 3;
        }
        #endregion

        #region Render
        /// <summary>
        /// Render quad to GraphicsDevice.
        /// </summary>
        public void Render()
        {
            m_GraphicsDevice.VertexDeclaration = m_VertexDeclaration;
            m_BasicEffect.Begin();
            foreach (EffectPass pass in m_BasicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                m_GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleList, m_Vertices, 0, 4, m_Indexes, 0, 2);
                pass.End();
            }
            m_BasicEffect.End();
        }
        #endregion
    }
}
